﻿using ElasticSearchTest.Common;
using ElasticSearchTest.Model;
using Nest;
using Newtonsoft.Json;
using NPOI.SS.Formula.Functions;
using PMS.Search.Domain.Common;
using PMS.Search.Domain.Entities;
using PMS.Search.Domain.QueryModel;
using PMS.Search.Elasticsearch;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ElasticSearchTest
{
    public class KeywordEsService
    {
        private readonly EsClient _client;
        private readonly EsIndexConfig.Index _index = EsIndexConfig.Instance.KeywordIndex;

        public KeywordEsService(EsClient client)
        {
            _client = client;
        }

        public void Test()
        {
            //var resp = CreateIndex();

            int type = 1;
            var search = SearchAsync(new SearchBaseQueryModel("塔村学", 1, 10)).GetAwaiter().GetResult();


            var keywords = new string[] { "测试", "测试2", "测试3" };
            var addResp = AddAsync(ToEntities(keywords)).GetAwaiter().GetResult();

            var addResp2 = AddOrUpdateAsync(ToEntities(keywords)).GetAwaiter().GetResult();
        }

        public IEnumerable<SearchKeyword> ToEntities(IEnumerable<string> keywords)
        {
            return keywords.Select(s => new SearchKeyword()
            {
                Keyword = s,
                UpdateTime = DateTime.Now,
                IsDeleted = false,
                ResultNumber = 0,
                ExtraPoint = 0
            });
        }

        public async Task<BulkResponse> AddOrUpdateAsync(IEnumerable<SearchKeyword> entities)
        {
            var createResponse = await _client.GetClient()
                .BulkAsync(b =>
                    b.IndexMany(entities, (o, entity) =>
                        o.Index(_index.SearchIndex).Document(entity).Id(entity.Keyword))
                );
            return createResponse;
        }

        /// <summary>
        /// failed if exists
        /// </summary>
        /// <param name="entities"></param>
        /// <returns></returns>
        public async Task<BulkResponse> AddAsync(IEnumerable<SearchKeyword> entities)
        {
            var createResponse = await _client.GetClient()
                .BulkAsync(b =>
                    b.Index(_index.SearchIndex)
                     .CreateMany(entities, (o, entity) => o.Document(entity).Id(entity.Keyword))
                );
            return createResponse;
        }

        public async Task AddBatchAsync(IEnumerable<string> keywords)
        {
            var entities = ToEntities(keywords);

            List<string> failKeywords = new List<string>();
            await BatchHelper.RunAsync(entities, async items =>
            {
                var resp = await AddOrUpdateAsync(items);
                if (resp.IsValid)
                {
                    failKeywords.AddRange(resp.Items.Where(s => !s.IsValid).Select(s => s.Id));
                }
                else
                {
                    failKeywords.AddRange(entities.Select(s => s.Keyword));
                }
                await Task.Delay(100);
            });
            if (failKeywords.Any())
            {
                //Console.WriteLine("上传失败数据:", JsonConvert.SerializeObject(failKeywords));
            }
        }

        public async Task<PaginationModel<SearchKeyword>> SearchAsync(SearchBaseQueryModel queryModel)
        {
            var keyword = queryModel.Keyword;
            int from = (queryModel.PageIndex - 1) * queryModel.PageSize;
            int size = queryModel.PageSize;

            var search = new SearchDescriptor<SearchKeyword>()
                .Index(new string[] { _index.SearchIndex })
                .Highlight(h => h.Fields(f => f.Field("keyword")))
                .Sort(sd => sd.Descending(t => t.ResultNumber).Descending(SortSpecialField.Score))
                .From(from)
                .Size(size);

            var mustQuery = new List<Func<QueryContainerDescriptor<SearchKeyword>, QueryContainer>>()
            {
                q=> q.Term(t => t.Field(p => p.IsDeleted).Value(false))
            };
            var constantQuery = new ConstantScoreQuery();

            //模糊查询
            if (!string.IsNullOrWhiteSpace(keyword))
            {
                var query = new MultiMatchQuery()
                {
                    Fields = Infer.Field("keyword"),
                    Query = keyword
                };

                mustQuery.Add(q => query);
            }

            search.Query(q => q.FunctionScore(csq => csq.Query(qc => qc.Bool(b => b.Must(mustQuery)))
                .Functions(sfd =>
                    sfd.FieldValueFactor(fff => fff.Field(f => f.ExtraPoint)
                    .Factor(1)
                    .Missing(0)
                    .Modifier(FieldValueFactorModifier.None)))
                .BoostMode(FunctionBoostMode.Sum)
            ));


            var result = await _client.GetClient().SearchAsync<SearchKeyword>(search);
            var total = result.Total;
            var data = result.Hits.Select(s => s.Source).ToList();
            var highs = result.Hits.Select(s => s.Highlight).ToList();

            HighlightHelper.SetHighlights(ref data, highs, "Keyword");
            return PaginationModel.Build(data, total);
        }

        public void TestCreate()
        {
            Func<CreateIndexDescriptor, ICreateIndexRequest> selector = c => c
                .Aliases(ad => ad.Alias(_index.Alias))
                .Settings(s => s
                    .Setting("max_ngram_diff", 20)
                    .Setting("max_result_window", 500000)
                    .Analysis(a => a
                        .CharFilters(cf => cf
                            .UserDefined("tsconvert", new TSConvertCharFilter("t2s", "#"))
                         )
                        .TokenFilters(tf => tf
                            .UserDefined("pinyin_max_filter",
                                new PinyinTokenFilter
                                {
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = true,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                            .UserDefined("pinyin_filter",
                                new PinyinTokenFilter
                                {
                                    KeepFirstLetter = true,
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = false,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                        )
                        .Analyzers(aa => aa
                            .Custom("ik_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_max_word")
                             )
                            .Custom("ik_search_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("lowercase")
                             )
                            .Custom("ik_pinyin_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_max_word")
                                 .Filters("pinyin_max_filter", "lowercase")
                             )
                            .Custom("ik_pinyin_search_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("pinyin_filter", "lowercase")
                             )
                        )
                ))
                .Map<SearchKeyword>(mm => mm
               .Dynamic(false)
                   .Properties(p => p
                       .Number(t => t.Name(n => n.ExtraPoint))
                       .Date(t => t.Name(n => n.UpdateTime))
                       .Boolean(t => t.Name(n => n.IsDeleted))
                       .Number(t => t.Name(n => n.ResultNumber))
                       .Text(t => t
                           .Name(n => n.Keyword)
                           .Fields(f => f.Text(ft
                                 => ft.Name("pinyin")
                                 .Store(false)
                                 .Analyzer("ik_pinyin_analyzer")
                                 .SearchAnalyzer("ik_pinyin_search_analyzer")
                           ))
                       )
                   )
               );

            var request = selector.Invoke(new CreateIndexDescriptor(_index.Name));
            var str = request.ToString();
            var queryStr = request.RequestParameters.QueryString;
        }

        public CreateIndexResponse CreateIndex()
        {
            var resp = _client.GetClient().Indices.Delete(_index.Name);
            return _client.GetClient().Indices.Create(_index.Name, c => c
                .Aliases(ad => ad.Alias(_index.Alias))
                .Settings(s => s
                    .Setting("max_ngram_diff", 20)
                    .Setting("max_result_window", 500000)
                    .Analysis(a => a
                        .CharFilters(cf => cf
                            .UserDefined("tsconvert", new TSConvertCharFilter("t2s", "#"))
                         )
                        .TokenFilters(tf => tf
                            .UserDefined("pinyin_max_filter",
                                new PinyinTokenFilter
                                {
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = true,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                            .UserDefined("pinyin_search_filter",
                                new PinyinTokenFilter
                                {
                                    KeepFirstLetter = false,
                                    KeepSeparateFirstLetter = false,
                                    KeepJoinedFullPinyin = true,
                                    KeepFullPinyin = false,
                                    KeepOriginal = true,
                                    LimitFirstLetterLength = 16,
                                    Lowercase = true,
                                    RemoveDuplicatedTerm = true
                                })
                            .EdgeNGram("edge_ngram_filter",
                                etf => etf.MinGram(1).MaxGram(50)
                            )
                            .NGram("ngram_filter",
                                etf => etf.MinGram(1).MaxGram(2)
                            )
                        )
                        .Analyzers(aa => aa
                            .Custom("ik_analyzer", ca => ca
                                .CharFilters("tsconvert")
                                .Tokenizer("ik_max_word")
                                .Filters("ngram_filter", "lowercase")
                            )
                            .Custom("ik_search_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("lowercase")
                             )
                            .Custom("ik_pinyin_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_max_word")
                                 .Filters("pinyin_max_filter", "lowercase")
                             )
                            .Custom("ik_pinyin_search_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("pinyin_search_filter", "lowercase")
                             )
                            .Custom("ik_edge_analyzer", ca => ca
                                 .CharFilters("tsconvert")
                                 .Tokenizer("ik_smart")
                                 .Filters("edge_ngram_filter", "lowercase")
                            )
                        )
                ))
                .Map<SearchKeyword>(mm => mm
                .Dynamic(false)
                   .Properties(p => p
                       .Number(t => t.Name(n => n.ExtraPoint))
                       .Date(t => t.Name(n => n.UpdateTime))
                       .Boolean(t => t.Name(n => n.IsDeleted))
                       .Number(t => t.Name(n => n.ResultNumber))
                       .Text(t =>
                            t.Name(n => n.Keyword)
                            .Fields(f =>
                                f.Text(ft =>
                                        ft.Name("cntext")
                                        .Analyzer("ik_analyzer")
                                        .SearchAnalyzer("ik_search_analyzer")
                                )
                                .Text(ft =>
                                        ft.Name("pinyin")
                                        .Analyzer("ik_pinyin_analyzer")
                                        .SearchAnalyzer("ik_pinyin_search_analyzer")
                                )
                                .Text(ft =>
                                        ft.Name("edge")
                                        .Analyzer("ik_edge_analyzer")
                                        .SearchAnalyzer("ik_search_analyzer")
                                )
                        ))
                   )
               )
            );

        }
    }
}
